{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# CW-Husky glitch exploration\n",
    "\n",
    "CW-Husky's FPGA includes a small logic analyzer which allows the glitch generation to be visualized. This can be helpful to understand how the glitch parameters influence the shape of the glitch.\n",
    "\n",
    "The glitch signals captured in this notebook are digital FPGA internal signals; if you're interested in the actual shape of the glitch output, you won't get that from this; you'll need a good analog oscilloscope instead.\n",
    "\n",
    "No target needs to be connected for this notebook.\n",
    "\n",
    "If you don't have a Husky, you can still use this notebook to understand how Husky generates glitches; skip down to the \"If you don't have Husky\" section. Skip over all the preceding cells.\n",
    "\n",
    "This is also a companion to test_husky.py, for when visual inspection of glitches is needed."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "SCOPE=\"OPENADC\"\n",
    "PLATFORM=\"CWHUSKY\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import chipwhisperer as cw\n",
    "scope = cw.scope(name='Husky')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%run \"../../Setup_Scripts/Setup_Generic.ipynb\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "scope.clock.clkgen_src = 'system'\n",
    "scope.clock.clkgen_freq = 10e6\n",
    "scope.clock.adc_mul = 1\n",
    "\n",
    "scope.adc.basic_mode = \"rising_edge\"\n",
    "\n",
    "scope.trigger.triggers = \"tio4\"\n",
    "scope.io.hs2 = \"clkgen\""
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Set up glitch:\n",
    "\n",
    "By default, the glitch generation logic is disabled, so it needs to be explicitely turned on."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "scope.glitch.enabled = True\n",
    "scope.glitch.clk_src = 'pll'\n",
    "scope.clock.pll.update_fpga_vco(600e6)\n",
    "scope.glitch.output = 'glitch_only'\n",
    "scope.glitch.trigger_src = 'manual'\n",
    "scope.glitch.repeat = 1"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "assert scope.glitch.mmcm_locked"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "One glitch parameter which is unique to Husky (compared to CW-lite and CW-pro) is the glitch MMCM VCO frequency. This sets the internal frequency of the FPGA glitch MMCMs, and must be between 600 MHz and 1200 MHz. The higher the frequency, the finer the phase adjustment steps (which control the glitch shape).\n",
    "\n",
    "`scope.glitch.phase_shift_steps` gives the number of phase shift steps in one clock cycle of `scope.glitch.clk_src` (which in our example here is 10 MHz). This is determined by the clock multiplier used to bring the glitch source clock to 600 MHz; that multiplier, multiplied by the constant 56, gives the number of phase shift steps per cycle. In our example, a clock multiplier of 60 is used to generate the 600 MHz VCO frequency, and 60 times 56 = 3360 steps.\n",
    "\n",
    "If this is too confusing, just remember that the number of phase shift steps per cycle depends on:\n",
    "1. the VCO frequency (higher frequency = more steps)\n",
    "2. the glitch source clock frequency (higher frequency = fewer steps)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "scope.glitch.phase_shift_steps"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Note that increasing the VCO frequency also increases the MMCM power consumption, which can actually account for the majority of the FPGA's power draw; you can actually see the FPGA temperature go up and down with the VCO frequency (with `scope.XADC.temp`). Keep this in mind for long glitching campaigns (if the FPGA starts getting too hot, red LEDs will flash, and the error condition will be noted on `scope.XADC.status`)."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Set up logic analyzer:\n",
    "\n",
    "The logic analyzer is an optional component which can be included or excluded in the FPGA build process, so first let's make sure it's there:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "assert scope.LA.present, 'There is no logic analyzer in this FPGA bitfile!'"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Like the glitch logic, the LA uses a fast MMCM which can draw significant power, so it's disabled by default.\n",
    "\n",
    "In order to catch narrow glitches, we need a high oversampling factor. The LA sampling clock is derived from the target clock. In this case we're oversampling at 40x.\n",
    "\n",
    "The target clock is 10 MHz, so the LA will be sampling at 400 MS/s. Technically this is faster than we should go since the FPGA bitfile is implemented to support a maximum sampling rate of 250 MS/s, but here in practice it works fine (YMMV if you're using Husky in extreme conditions!).\n",
    "\n",
    "Finally we set the `capture_group` to capture the glitch signals (the LA can also be used to capture USERIO or 20-pin connector signals), and we set the capture trigger to be the glitch itself.\n",
    "\n",
    "See `help(scope.LA)` for more on these and other capture parameters."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "scope.LA.enabled = True\n",
    "scope.LA._warning_frequency = 401e6 # prevent warning message that we're setting the sampling clock too high\n",
    "scope.LA.oversampling_factor = 40\n",
    "scope.LA.downsample = 1\n",
    "scope.LA.capture_group = 'glitch'\n",
    "scope.LA.trigger_source = \"glitch_source\"\n",
    "scope.LA.capture_depth = 512"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "assert scope.LA.locked"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Single capture:\n",
    "\n",
    "Let's first do a simple single capture.\n",
    "\n",
    "We can pick arbitrary glitch `offset` and `width` parameters; start with the values below, then explore other values."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# adjust as you wish:\n",
    "scope.glitch.offset = 1000\n",
    "scope.glitch.width = 1000"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Whereas CW-lite and CW-pro have \"coarse\" and \"fine\" settings for each parameter, with Husky there is a single setting, which is the number of phase shift steps. This can be a positive or negative number, and it is allowed to roll over (e.g. with the default settings in this notebook, `scope.glitch.phase_shift_steps = 3360`; it's possible to set `offset` or `width` to, for example, 4000, which would be equivalent to 4000-3360 = 640).\n",
    "\n",
    "While this is a change from CW-lite/pro that's not backwards compatible, it makes it much easier to adjust and understand `offset` and `width`.\n",
    "\n",
    "(This change is due to architectural differences in the Xilinx FPGAs: CW-lite/pro have a Spartan6 FPGA and use DCMs to generate glitches; CW-Husky has an Artix7 FPGA and its more powerful MMCMs to generate glitches.)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "scope.LA.arm()\n",
    "scope.glitch.manual_trigger()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "After `manual_trigger()`, the collected data is ready to be read via `read_capture_data()`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "raw = scope.LA.read_capture_data()\n",
    "glitchout    = scope.LA.extract(raw, 0)\n",
    "source       = scope.LA.extract(raw, 1)\n",
    "mmcm1out     = scope.LA.extract(raw, 2)\n",
    "mmcm2out     = scope.LA.extract(raw, 3)\n",
    "glitchgo     = scope.LA.extract(raw, 4)\n",
    "glitchenable = scope.LA.extract(raw, 6)\n",
    "glitchsource = scope.LA.extract(raw, 7)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "And now we plot:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from bokeh.plotting import figure, show\n",
    "from bokeh.resources import INLINE\n",
    "from bokeh.io import output_notebook\n",
    "from bokeh.models import Span, Legend, LegendItem\n",
    "import numpy as np\n",
    "output_notebook(INLINE)\n",
    "\n",
    "o = figure(width=1800)\n",
    "\n",
    "xrange = list(range(len(source)))\n",
    "O1 = o.line(xrange, source + 6, line_color='black')\n",
    "O2 = o.line(xrange, mmcm1out + 4, line_color='blue')\n",
    "O3 = o.line(xrange, mmcm2out + 2, line_color='red')\n",
    "O4 = o.line(xrange, glitchout + 0, line_color='purple', line_width=2)\n",
    "O5 = o.line(xrange, glitchenable - 2, line_color='black', line_width=2)\n",
    "#O6 = o.line(xrange, glitchgo - 4, line_color='green')\n",
    "#O7 = o.line(xrange, glitchsource - 6, line_color='pink', line_width=2)\n",
    "\n",
    "legend = Legend(items=[\n",
    "    LegendItem(label='source clock', renderers=[O1]),\n",
    "    LegendItem(label='glitch MMCM1 output (internal signal)', renderers=[O2]),\n",
    "    LegendItem(label='glitch MMCM2 output (internal signal)', renderers=[O3]),\n",
    "    LegendItem(label='glitch clock output', renderers=[O4]),\n",
    "    LegendItem(label='glitch enable', renderers=[O5]),\n",
    "    #LegendItem(label='glitch go', renderers=[O6]),\n",
    "    #LegendItem(label='glitch trigger source', renderers=[O7]),\n",
    "])\n",
    "o.add_layout(legend)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's add some handy markers to help visualize how the glitch is constructed.\n",
    "\n",
    "The glitch clock output is high when MMCM1, MMCM2, and glitch enable are all high. (Because `scope.glitch.output = \"glitch_only\"` in this example. You can change this setting to observe the different behaviours that are possible.)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# add glitch markers:\n",
    "def find_transitions(data, pattern):\n",
    "    return [i for i in range(0,len(data)) if list(data[i:i+len(pattern)])==pattern]\n",
    "\n",
    "transitions = [find_transitions(glitchout, [0,1])[0]+1, find_transitions(glitchout, [1,0])[0]]\n",
    "\n",
    "for b in transitions:\n",
    "    o.renderers.extend([Span(location=b, dimension='height', line_color='black', line_width=1, line_dash='dashed')])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "show(o)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "You can explore the effect of different `scope.glitch` parameters (such as `width`, `offset`, and `output`) and re-run the above from the `manual_trigger()` call onwards."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Interactive glitch visualization:\n",
    "\n",
    "Now we step through many width/offset combinations so that we can interactively plot them.\n",
    "We carry out STEPS * STEPS captures. STEPS can be whatever you want, but there it doesn't make sense to make STEPS greater that `scope.LA.oversampling_factor`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "STEPS = scope.LA.oversampling_factor\n",
    "increment = scope.glitch.phase_shift_steps // STEPS\n",
    "start = 0\n",
    "\n",
    "import numpy as np\n",
    "glitchouts = np.zeros((STEPS, STEPS, scope.LA.capture_depth))\n",
    "sources    = np.zeros((STEPS, STEPS, scope.LA.capture_depth))\n",
    "mmcm1outs  = np.zeros((STEPS, STEPS, scope.LA.capture_depth))\n",
    "mmcm2outs  = np.zeros((STEPS, STEPS, scope.LA.capture_depth))\n",
    "glitchenables = np.zeros((STEPS, STEPS, scope.LA.capture_depth))\n",
    "glitchgo = np.zeros((STEPS, STEPS, scope.LA.capture_depth))\n",
    "\n",
    "from tqdm.notebook import tnrange\n",
    "\n",
    "scope.glitch.offset = start\n",
    "scope.glitch.width = start\n",
    "\n",
    "for o in tnrange(STEPS):\n",
    "    scope.glitch.width = start\n",
    "    for w in range(STEPS):\n",
    "        scope.LA.arm()\n",
    "        scope.glitch.manual_trigger() \n",
    "        raw = scope.LA.read_capture_data()\n",
    "        glitchouts[o][w]   = scope.LA.extract(raw, 0)\n",
    "        sources[o][w]      = scope.LA.extract(raw, 1)\n",
    "        mmcm1outs[o][w]    = scope.LA.extract(raw, 2)\n",
    "        mmcm2outs[o][w]    = scope.LA.extract(raw, 3)\n",
    "        glitchgo[o][w]     = scope.LA.extract(raw, 4)\n",
    "        glitchenables[o][w] = scope.LA.extract(raw, 6)\n",
    "        scope.glitch.width += increment\n",
    "    scope.glitch.width = start\n",
    "    scope.glitch.offset += increment"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#np.savez_compressed('data/husky_glitch_data.npz', array=np.asarray([glitchouts, sources, mmcm1outs, mmcm2outs, glitchgo, glitchenables]))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**If you don't have a Husky**, run this to load previously saved glitch waveform data generated by a Husky:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# this is only needed if you don't have a Husky\n",
    "try:\n",
    "    type(scope) == 'chipwhisperer.capture.scopes.OpenADC.OpenADC'\n",
    "except:\n",
    "    from bokeh.plotting import figure, show\n",
    "    from bokeh.resources import INLINE\n",
    "    from bokeh.io import output_notebook\n",
    "    import numpy as np\n",
    "    output_notebook(INLINE)\n",
    "\n",
    "    def find_transitions(data, pattern):\n",
    "        return [i for i in range(0,len(data)) if list(data[i:i+len(pattern)])==pattern]\n",
    "\n",
    "    alls = np.load('../data/husky_glitch_data.npz')\n",
    "    [glitchouts, sources, mmcm1outs, mmcm2outs, glitchgo, glitchenables] = alls['array']\n",
    "\n",
    "    STEPS = len(glitchouts)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def update_plot(offset, width):\n",
    "    S1.data_source.data['y'] = sources[offset][width] + 6\n",
    "    S2.data_source.data['y'] = mmcm1outs[offset][width] + 4\n",
    "    S3.data_source.data['y'] = mmcm2outs[offset][width] + 2 \n",
    "    S4.data_source.data['y'] = glitchouts[offset][width] + 0\n",
    "    S5.data_source.data['y'] = glitchenables[offset][width] - 2\n",
    "    #S6.data_source.data['y'] = glitchgo[offset][width] - 4\n",
    "\n",
    "    t1s = find_transitions(glitchouts[offset][width], [0,1])\n",
    "    t2s = find_transitions(glitchouts[offset][width], [1,0])\n",
    "    if len(t1s) == 1:\n",
    "        T1.location = t1s[0]+1\n",
    "    else:\n",
    "        T1.location = 0\n",
    "\n",
    "    if len(t2s) == 1:\n",
    "        T2.location = t2s[0]\n",
    "    else:\n",
    "        T2.location = 0\n",
    "\n",
    "    \n",
    "    push_notebook()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from ipywidgets import interact, Layout\n",
    "from bokeh.io import push_notebook\n",
    "from bokeh.models import Span, Legend, LegendItem\n",
    "\n",
    "o = 0\n",
    "w = 0\n",
    "\n",
    "S = figure(width=1800)\n",
    "\n",
    "xrange = list(range(len(sources[o][w])))\n",
    "S1 = S.line(xrange, sources[o][w] + 6, line_color='black')\n",
    "S2 = S.line(xrange, mmcm1outs[o][w] + 4, line_color='blue')\n",
    "S3 = S.line(xrange, mmcm2outs[o][w] + 2 , line_color='red')\n",
    "S4 = S.line(xrange, glitchouts[o][w] + 0, line_color='purple', line_width=2)\n",
    "S5 = S.line(xrange, glitchenables[o][w] - 2, line_color='green')\n",
    "#S6 = S.line(xrange, glitchgo[o][w] - 4, line_color='pink', line_width=2)\n",
    "\n",
    "t1s = find_transitions(glitchouts[o][w], [0,1])\n",
    "t2s = find_transitions(glitchouts[o][w], [1,0])\n",
    "if len(t1s) == 1:\n",
    "    T1_location = t1s[0]+1\n",
    "else:\n",
    "    T1_location = 0\n",
    "\n",
    "if len(t2s) == 1:\n",
    "    T2_location = t2s[0]\n",
    "else:\n",
    "    T2_location = 0\n",
    "        \n",
    "T1 = Span(location=T1_location, dimension='height', line_color='black', line_dash='dashed', line_width=1)\n",
    "T2 = Span(location=T2_location, dimension='height', line_color='black', line_dash='dashed', line_width=1)\n",
    "\n",
    "legend = Legend(items=[\n",
    "    LegendItem(label='source clock', renderers=[S1]),\n",
    "    LegendItem(label='glitch MMCM1 output', renderers=[S2]),\n",
    "    LegendItem(label='glitch MMCM2 output', renderers=[S3]),\n",
    "    LegendItem(label='glitch clock output', renderers=[S4]),\n",
    "    LegendItem(label='glitch enable', renderers=[S5]),\n",
    "    #LegendItem(label='glitch go', renderers=[S6]),\n",
    "])\n",
    "\n",
    "S.add_layout(legend)\n",
    "S.add_layout(T1)\n",
    "S.add_layout(T2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "show(S, notebook_handle=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "interact(update_plot, offset=(0, STEPS-1), width=(0, STEPS-1))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This concludes the exploratory portion of this notebook.\n",
    "\n",
    "In the sections that follow, we show how the internal logic analyzer can be used to validate that glitches are being generated correctly.\n",
    "\n",
    "If you're done, turn off MMCMs to allow the FPGA to cool down:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "scope.LA.enabled = False\n",
    "scope.glitch.enabled = False"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.7"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
